﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;

namespace Twofolders
{
    internal class BackupRunner
    {
        private FolderPair m_folderPair;

        private System.Windows.Forms.ListViewItem m_listViewItem;

        private BackgroundWorker m_backgroundWorker;

        private string m_typeOfProgress;

        private List<string> m_errors = new List<string>();

        private int m_fileProgressCount = 0;

        private int m_directoryProgressCount = 0;

        private int m_totalFileCount = 0;

        private int m_totalDirectoryCount = 0;

        private string m_fileInProgress = "";

        private System.Windows.Forms.NotifyIcon m_notifyIcon;

        private static List<BackupRunner> s_activeBackupRunners = new List<BackupRunner>();

        private const int SLEEP_PERIOD = 500;

        private const string CANCELED = "Canceled";

        public BackupRunner(FolderPair folderPair, System.Windows.Forms.ListViewItem item, 
            System.Windows.Forms.NotifyIcon notifyIcon)
        {
            m_folderPair = folderPair;
            m_listViewItem = item;
            m_notifyIcon = notifyIcon;
        }

        static public void RunBackup(Dictionary<FolderPair, System.Windows.Forms.ListViewItem> folderPairItems, 
            System.Windows.Forms.NotifyIcon notifyIcon)
        {
            foreach (KeyValuePair<FolderPair, System.Windows.Forms.ListViewItem> folderPairItem in folderPairItems)
            {
                bool isAlreadyRunning = false;
                foreach (BackupRunner runner in s_activeBackupRunners)
                {
                    if (runner.m_folderPair.Name == folderPairItem.Key.Name)
                    {
                        isAlreadyRunning = true;
                        break;
                    }
                }

                if(!isAlreadyRunning) s_activeBackupRunners.Add(new BackupRunner(
                    folderPairItem.Key, folderPairItem.Value, notifyIcon));
            }

            if (s_activeBackupRunners[0].m_backgroundWorker == null) s_activeBackupRunners[0].Run();

            for (int i = 1; i < s_activeBackupRunners.Count; i++)
            {
                s_activeBackupRunners[i].m_listViewItem.SubItems[3].Text = "Waiting...";
            }
        }

        static public void StopBackup(FolderPair folderPair)
        {
            foreach (BackupRunner runner in s_activeBackupRunners)
            {
                if (runner.m_folderPair.Name == folderPair.Name)
                {
                    if (runner.m_backgroundWorker == null)
                    {
                        runner.m_listViewItem.SubItems[3].Text = CANCELED;
                        s_activeBackupRunners.Remove(runner);
                    }
                    else
                    {
                        runner.m_backgroundWorker.CancelAsync();
                    }
                    break;
                }
            }
        }

        private void Run()
        {
            m_backgroundWorker = new BackgroundWorker();
            m_backgroundWorker.WorkerReportsProgress = true;
            m_backgroundWorker.WorkerSupportsCancellation = true;
            m_backgroundWorker.DoWork += new DoWorkEventHandler(BackgroundWorker_DoWork);
            m_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
            m_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
            m_backgroundWorker.RunWorkerAsync();
        }

        private void BackgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            BackgroundWorker backgroundWorker = sender as BackgroundWorker;            

            m_typeOfProgress = "";
            DetermineFileAndDirectoryCount(new DirectoryInfo(m_folderPair.SourceDirectory), backgroundWorker, e);

            System.Threading.Thread.Sleep(SLEEP_PERIOD);

            m_typeOfProgress = "Copy";
            PerformBackup(new DirectoryInfo(m_folderPair.SourceDirectory), backgroundWorker, e);

            System.Threading.Thread.Sleep(SLEEP_PERIOD);

            m_typeOfProgress = "Delete";
            m_fileProgressCount = m_directoryProgressCount = 0;
            if (m_folderPair.ShouldDeleteFromDestination) DeleteFromDestination(
                new DirectoryInfo(m_folderPair.DestinationDirectory), backgroundWorker, e);
        }

        private void BackgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            string fileInProgress = (string.IsNullOrEmpty(m_fileInProgress)) ? "" : m_fileInProgress + ", ";
            m_listViewItem.SubItems[3].Text = string.Format("{4} Progress: {5}{0}/{1} files, {2}/{3} directories...",
                m_fileProgressCount, m_totalFileCount, m_directoryProgressCount,
                m_totalDirectoryCount, m_typeOfProgress, fileInProgress);
            m_fileInProgress = "";
        }
        
        private void BackgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            s_activeBackupRunners.Remove(this);

            if (e.Cancelled)
            {
                m_listViewItem.SubItems[3].Text = CANCELED;
            }
            else
            {
                m_listViewItem.SubItems[3].Text = string.Format("Done at {0}", DateTime.Now.ToString());
            }

            if (m_errors.Count > 0) new BackupErrorForm(m_errors, m_folderPair.Name).Show();

            if (s_activeBackupRunners.Count > 0) s_activeBackupRunners[0].Run();
            else if (s_activeBackupRunners.Count == 0 && m_notifyIcon != null)
                m_notifyIcon.Icon = Properties.Resources.TwofoldersIcon;
        }

        private void DetermineFileAndDirectoryCount(DirectoryInfo sourceDirectory, BackgroundWorker backgroundWorker,
            DoWorkEventArgs e)
        {
            if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;

            foreach (DirectoryInfo childDirectory in sourceDirectory.GetDirectories(
                "*", SearchOption.TopDirectoryOnly))
            {
                DetermineFileAndDirectoryCount(childDirectory, backgroundWorker, e);
            }

            foreach (FileInfo sourceFile in sourceDirectory.GetFiles("*", SearchOption.TopDirectoryOnly))
            {
                m_totalFileCount += 1;
                ReportBackgroundWorkerProgress(backgroundWorker);
            }

            m_totalDirectoryCount += 1;
            ReportBackgroundWorkerProgress(backgroundWorker);
        }

        private void PerformBackup(DirectoryInfo sourceDirectory, BackgroundWorker backgroundWorker, DoWorkEventArgs e)
        {
            if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;

            foreach (DirectoryInfo childDirectory in sourceDirectory.GetDirectories(
                "*", SearchOption.TopDirectoryOnly))
            {
                PerformBackup(childDirectory, backgroundWorker, e);
            }

            string destinationDirectoryString = m_folderPair.DestinationDirectory +
                sourceDirectory.FullName.Substring(m_folderPair.SourceDirectory.Length);

            if (Directory.Exists(destinationDirectoryString))
            {
                DirectoryInfo destinationDirectory = new DirectoryInfo(destinationDirectoryString);
                foreach (FileInfo sourceFile in sourceDirectory.GetFiles("*", SearchOption.TopDirectoryOnly))
                {
                    if (File.Exists(destinationDirectory.FullName + Path.DirectorySeparatorChar + sourceFile.Name))
                    {
                        FileInfo destinationFile = new FileInfo(
                            destinationDirectory.FullName + Path.DirectorySeparatorChar + sourceFile.Name);
                        if (sourceFile.Length != destinationFile.Length || sourceFile.LastWriteTime != destinationFile.LastWriteTime)
                        {
                            m_fileInProgress = sourceFile.Name;
                            ReportBackgroundWorkerProgress(backgroundWorker);
                            TryCopyFile(sourceFile.FullName,
                                destinationDirectory.FullName + Path.DirectorySeparatorChar + sourceFile.Name, true);                            
                        }
                    }
                    else
                    {
                        m_fileInProgress = sourceFile.Name;
                        ReportBackgroundWorkerProgress(backgroundWorker);
                        TryCopyFile(sourceFile.FullName,
                            destinationDirectory.FullName + Path.DirectorySeparatorChar + sourceFile.Name, false);                        
                    }

                    m_fileProgressCount += 1;
                    if(ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;
                }
            }
            else
            {
                DirectoryInfo destinationDirectory = Directory.CreateDirectory(destinationDirectoryString);
                foreach (FileInfo sourceFile in sourceDirectory.GetFiles("*", SearchOption.TopDirectoryOnly))
                {
                    m_fileInProgress = sourceFile.Name;
                    ReportBackgroundWorkerProgress(backgroundWorker);
                    TryCopyFile(sourceFile.FullName,
                        destinationDirectory.FullName + Path.DirectorySeparatorChar + sourceFile.Name, false);                    
                    m_fileProgressCount += 1;
                    if(ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;
                }
            }

            m_directoryProgressCount += 1;
            ReportBackgroundWorkerProgress(backgroundWorker);
            if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;
        }

        private void DeleteFromDestination(DirectoryInfo destinationDirectory, BackgroundWorker backgroundWorker,
            DoWorkEventArgs e)
        {
            if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;

            foreach (DirectoryInfo childDirectory in destinationDirectory.GetDirectories(
                "*", SearchOption.TopDirectoryOnly))
            {
                DeleteFromDestination(childDirectory, backgroundWorker, e);
            }

            string sourceDirectoryString = m_folderPair.SourceDirectory +
                destinationDirectory.FullName.Substring(m_folderPair.DestinationDirectory.Length);

            if (Directory.Exists(sourceDirectoryString))
            {
                DirectoryInfo sourceDirectory = new DirectoryInfo(sourceDirectoryString);

                foreach (FileInfo destinationFile in destinationDirectory.GetFiles("*", SearchOption.TopDirectoryOnly))
                {
                    if (!File.Exists(sourceDirectory.FullName + Path.DirectorySeparatorChar + destinationFile.Name))
                    {
                        m_fileInProgress = destinationFile.Name;
                        ReportBackgroundWorkerProgress(backgroundWorker);
                        TryDeleteFile(destinationFile.FullName);
                    }

                    m_fileProgressCount += 1;
                    if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;
                }
            }
            else
            {
                TryDeleteDirectory(destinationDirectory.FullName);
            }

            m_directoryProgressCount += 1;
            ReportBackgroundWorkerProgress(backgroundWorker);
            if (ShouldBackgroundWorkerBeCanceled(backgroundWorker, e)) return;
        }

        private void TryCopyFile(string source, string destination, bool shouldOverwrite)
        {
            try
            {
                File.Copy(source, destination, shouldOverwrite);
            }
            catch (Exception ex)
            {
                //TODO[kabiraman]: log errors
                m_errors.Add(ex.Message);
            }
        }

        private void TryDeleteFile(string filePath)
        {
            try
            {
                File.Delete(filePath);
            }
            catch (Exception ex)
            {
                //TODO[kabiraman]: log errors
                m_errors.Add(ex.Message);
            }
        }

        private void TryDeleteDirectory(string path)
        {
            try
            {
                Directory.Delete(path, true);
            }
            catch (Exception ex)
            {
                m_errors.Add(ex.Message);
            }
        }

        private void ReportBackgroundWorkerProgress(BackgroundWorker backgroundWorker)
        {
            if (backgroundWorker != null)
            {
                backgroundWorker.ReportProgress(0);
                
            } 
        }

        private bool ShouldBackgroundWorkerBeCanceled(BackgroundWorker backgroundWorker, DoWorkEventArgs e)
        {
            if (backgroundWorker != null && backgroundWorker.CancellationPending)
            {
                e.Cancel = true;
                return true;
            }
            return false;
        }
    }
}
